package helpmeigor;

import com.google.wave.api.Annotation;
import com.google.wave.api.Blip;
import com.google.wave.api.Range;
import com.google.wave.api.TextView;

import java.util.logging.Logger;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.util.ArrayList;

import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;

public class ReferenceHelper {
	private static final Logger logging = Logger.getLogger(HelpmeigorServlet.class.getName());
	private Pattern referencePattern = Pattern.compile("\\[(\\d+?)\\]",  Pattern.CASE_INSENSITIVE | Pattern.DOTALL | Pattern.MULTILINE);

	// counts the number of references attached to this document.
	public int countReferences(Blip blip) {
		int count = 0;
		for (Annotation a: blip.getDocument().getAnnotations()) {
			if (a.getName().contains("igor_reference_")) {
				count++;
				//logging.info("Got annotation " + a.getName());
			}
		} 
		return count;
	}
	
	private void deleteReferencesBlock(Blip blip) {
		// delete the block of references at the end of the blip.
		TextView textView = blip.getDocument();
		int referencesStartAt = getReferencesBlockStart(blip);
		if (referencesStartAt >= 0) {
			textView.delete(new Range(referencesStartAt, textView.getText().length() - 1));
		} else {
			logging.info("Can't find igor_refblock_starts_at annotation.");
		}
		textView.deleteAnnotations("igor_refblock_starts_at");
	}
	
	public int getReferencesBlockStart(Blip blip) {
		// get start of references block. Relies on the "refblock_starts_at" annotation being intact.
		TextView textView = blip.getDocument();
		int referencesStartAt = - 1;
		
		for (Annotation temp: textView.getAnnotations("igor_refblock_starts_at")) {
			referencesStartAt = Integer.valueOf(temp.getRange().getStart()).intValue();
		}
		return referencesStartAt;
	}
	
	private void renderReferencesBlock(Blip blip) {
		// create a block of references at the end of the blip.
		TextView textView = blip.getDocument();		
		
		int startsAt = textView.getText().length() - 1;
		if (startsAt < 0) {startsAt = 0;} // for sanity, though in theory this should never happen.
				
		// now add individual references.		
		logging.info("Rendering " + String.valueOf(countReferences(blip)) + " references");
		int currentReference = 1;
		int numberOfReferences = countReferences(blip);
		
		if (numberOfReferences <= 0) {return;}
		
		String markupToAdd = "";
		while (currentReference <= numberOfReferences) {
			for (Annotation a: textView.getAnnotations("igor_reference_" + String.valueOf(currentReference))) {
				// there should only be one...
				markupToAdd = markupToAdd + "<li> [" + a.getName().substring(15) + "] " + a.getValue() + "</li>";
				// not sure if a break here exits the for or the while loop...
				//logging.info(".. found annotation for " + a.getName());
			}
			currentReference++;
		}
		textView.appendMarkup(" <br/><h3>References</h3><ul>" + markupToAdd + "</ul>");
		
		// annotate this as the beginning of the references block.
 		int endsAt = textView.getText().length();		
		textView.setAnnotation(new Range(startsAt, endsAt), "igor_refblock_starts_at", String.valueOf(startsAt));
	}
	
	public void rebuildReferences(Blip blip) {
		deleteReferencesBlock(blip);
		renderReferencesBlock(blip);
	}
		
	public boolean replaceReference(Blip blip, Integer a, Integer b) {
		TextView document = blip.getDocument();
		//logging.info(a.toString() + " is about to be moved to " + b.toString());
		
		// replace in text
		String aPattern = "\\[" + a.toString() + "\\]";
		Pattern replacePattern = Pattern.compile(aPattern,  Pattern.CASE_INSENSITIVE | Pattern.DOTALL | Pattern.MULTILINE);
		String replaceWith = "[" + b.toString() + "]";
		Matcher refMatcher = replacePattern.matcher(document.getText());
		int refBlockStart = getReferencesBlockStart(blip);
		
		if (refMatcher.find()) {
			// only continue if the match isn't in the references block.
			if ((refMatcher.start() < refBlockStart) && (refBlockStart >= 0)) {
				//logging.info("Found a match for " + aPattern + " between " + String.valueOf(refMatcher.start()) + " and " + String.valueOf(refMatcher.end()));
				document.delete(new Range(refMatcher.start(), refMatcher.end())); 
				// assuming this changes the positions across the whole document.
				document.insert(refMatcher.start(), replaceWith);
				return replaceReference(blip, a, b); // call function recursively until we've replaced everything in the text.
			}
		}
		
		// replace in annotation
		// save a copy first...
		String fullCitation = getValueForAnnotation(document, a);
		// delete it.
		String annotationName = "igor_reference_" + a.toString();
		document.deleteAnnotations(annotationName);
		// sanity check...
		if (fullCitation.length() <= 0) {
			logging.severe("Missing a citation annotation for " + a.toString());
			fullCitation = "missing citation";
		}
		// and then save to annotation b.
		annotationName = "igor_reference_" + b.toString();
		document.setAnnotation(annotationName, fullCitation);
	
		return true;
	}
		
	private String getValueForAnnotation(TextView document, Integer reference) {
		String fullCitation = "";
		// save a copy first...
		String annotationName = "igor_reference_" + reference.toString();
		for (Annotation temp: document.getAnnotations(annotationName)) {fullCitation = temp.getValue();}
		return fullCitation;
	}
	
	public boolean traverseTextReferences(Blip blip, Integer initialExpectedNumber) {
		boolean didFixSomething = false;
		TextView document = blip.getDocument();
		Matcher refMatcher = referencePattern.matcher(document.getText());
		Integer expectedNumber = initialExpectedNumber;
		while (refMatcher.find()) {
			Integer foundNumber = Integer.valueOf(refMatcher.group(1));
			
			// sanity check: make sure that there's an annotation associated with this reference number.
			String fullCitation = getValueForAnnotation(document, foundNumber);
			if (fullCitation.length() <= 0) {
				logging.severe("found a reference number for which there is no associated annotation.");
				String annotationName = "igor_reference_" + foundNumber.toString();
				document.setAnnotation(annotationName, "missing citation");
			}
			
			if (foundNumber.intValue() == expectedNumber.intValue()) {
				//logging.info("found and expected: " + foundNumber.toString() + " == " + expectedNumber.toString());
				// ignore this but increment expected number.
				expectedNumber++;
			} else if (foundNumber.intValue() > expectedNumber.intValue()) {
				//logging.info("found and expected: " + foundNumber.toString() + " > " + expectedNumber.toString());
				// change current eN to 1000 * eN
				replaceReference(blip, expectedNumber, expectedNumber * 1000);
				
				// change fN to eN
				replaceReference(blip, foundNumber, expectedNumber);
				
				// increment expected number.
				expectedNumber++;	
				return traverseTextReferences(blip, expectedNumber);
			} else {
				//logging.info("found and expected: " + foundNumber.toString() + " < " + expectedNumber.toString());
				// foundNumber < expectedNumber
				// ignore this case (it's a repeat of an earlier reference)
			}
		}
		
		if (initialExpectedNumber > 1) {didFixSomething = true;} // must have changed something if we've looped recursively.
		return didFixSomething;
	}
	
	public String callLookupProxy(String query, String username, String service) {
		String result = "Sorry, couldn't contact the proxy server to look up " + query;
		try {
            URL url = new URL("http://174.143.232.228/proxy.php?" + 
            		"username=" + URLEncoder.encode(username, "UTF-8") + 
            		"&service=" + URLEncoder.encode(service, "UTF-8") + 
            		"&q=" + URLEncoder.encode(query, "UTF-8") + 
            		"&nocache=" + String.valueOf(System.currentTimeMillis())
            	);
                        
            
            BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream()));
            String data = "";
            String line = "";
            
            while ((line = reader.readLine()) != null) {data = data + line;}
            reader.close();
       
            if (data.length() > 0) {result = data;}
            
        } catch (MalformedURLException e) {
            logging.severe("MalformedURLException when querying " + query);
        } catch (IOException e) {
            logging.severe("IOException when querying " + query);
        }
        
		return result;
	}
	
	
	private boolean validateAnnotationReferences(Blip blip) {
		boolean didFixSomething = false;
		TextView document = blip.getDocument();
		
		ArrayList<String> annotationsToDelete = new ArrayList<String>();
		
		// delete any annotation references that aren't mentioned in the text any more.
		for (Annotation temp: document.getAnnotations()) {
			if (temp.getName().contains("igor_reference_")) {
				String referenceString = temp.getName().substring(15);
				//logging.info("Checking validity of reference annotation " + referenceString);
				int whereInText = document.getText().indexOf("[" + referenceString + "]");
				
				// ignore matches in the references block.
				int referencesBlockStart = getReferencesBlockStart(blip);
				if ((whereInText > referencesBlockStart) && (referencesBlockStart >= 0)) {whereInText = -1;}
				
				if (whereInText < 0) {
					// this annotation is invalid.
					//logging.info("... ok, it's invalid. Add to list to remove.");
					annotationsToDelete.add(temp.getName());
				}
				
				// todo: should probably check for duplicates here too.
			}
		}
		
		for (String key: annotationsToDelete) {
			logging.info("Deleting annotation with key " + key);
			document.deleteAnnotations(key);
			didFixSomething = true;
		}
		
		return didFixSomething;
	}
	
	public Integer haveAlreadyCited(Blip blip, String fullCitation) {
		Integer existingReference = -1;
		TextView document = blip.getDocument();
		
		for (Annotation temp: document.getAnnotations()) {
			if (temp.getName().contains("igor_reference_")) {
				String referenceString = temp.getName().substring(15);
				if (temp.getValue().compareToIgnoreCase(fullCitation) == 0) {
					// found a match.
					existingReference = Integer.valueOf(referenceString);
				}
			}
		}

		return existingReference;
	}
	
	public boolean checkExistingReferences(Blip blip) {
		// check that:
		// (a) references in text are in order
		// (b) that the order is continuous (no missing numbers)
		// (c) that there aren't any missing annotations
		// (d) that there aren't too many annotations
		
		// return true if we had to delete or change anything.
		boolean fixedWhileTraversing = traverseTextReferences(blip, 1);
		boolean fixedWhileValidating = validateAnnotationReferences(blip);
		
		return (fixedWhileTraversing || fixedWhileValidating);
	}
}
